#include "linkdb.hh"

#include <boost/bind.hpp>
#include <boost/shared_array.hpp>
#include <boost/shared_ptr.hpp>
#include <sstream>


#include "assert.hh"
#include "netinet++/ethernet.hh"
#include "vlog.hh"
#include <string.h>
#include "fnv_hash.hh"
#include <flow-expired.hh>
#include <flow-stats-in.hh>
#include "flow.hh"
#include "openflow/openflow/openflow.h"
#define FLOW_TIMEOUT        5
#define CHECK_INTERVAL	    5*FLOW_TIMEOUT
#define BROADCAST_TIMEOUT   5

#define DP_FROM_AP(loc) ((loc) & 0xffffffffffffULL)

/** \brief Class for Link Database
 *
 * This component saves all the flow entries installed in the edge switches.
 * When host is moved, Mobility Management will use this database to find out 
 * which flows they have to reroute.
 *    
 * Copyright (C) Stanford University, 2009.
 * @author Te-Yuan Huang
 * @date Feburary 2009
 */
using namespace vigil::container;
namespace vigil {
	namespace applications {

	static Vlog_module log("linkdb");
	
	void LinkDB::getInstance(const container::Context * ctxt,
			LinkDB*& s)
	{
		s = dynamic_cast<LinkDB*>
			(ctxt->get_by_interface(container::Interface_description
						(typeid(LinkDB).name())));
	}
	
	std::list<FlowEntryPtr> LinkDB::getFlowList(datapathid dp, uint16_t outport){
		std::list<FlowEntryPtr> flows;
		uint32_t hash_dp_port = hash_flow_entry(dp, outport);
		LinkTable::iterator ltit = link_table.find(hash_dp_port);
		if(ltit != link_table.end()){
			DB::iterator dbit = ltit->second.begin();
			for(;dbit!= ltit->second.end(); dbit++){
				flows.push_back(dbit->second);
			}
		}
		return flows;
	}

	int LinkDB::linkFlowNum(datapathid dp, uint16_t outport){
		uint32_t hash_dp_port = hash_flow_entry(dp, outport);
		LinkTable::iterator ltit = link_table.find(hash_dp_port);
		if(ltit != link_table.end()){
			return ltit->second.size();
		}
		return 0;
	}

	Flow LinkDB::create_flow_instance(FlowEntryPtr fe){
		ofp_flow_mod *ofp = (ofp_flow_mod*)fe->flow_mod_cmd.get();
		ofp_match ofm = ofp->match;
		log.dbg("Create Flow instance for :");
		print_flowEntry(fe.get());
		Flow flow(ofm.in_port, ofm.dl_vlan,
				fe->srcmac, fe->dstmac,
				ofm.dl_type,
				fe->srcip, fe->dstip,
				ofm.nw_proto, ofm.tp_src, ofm.tp_dst);
		return flow;
	}

	/**
	 * checkFlowEntry: periodically check flow entry with openflow switches
	 */
	void LinkDB::checkFlowEntry(void){
		struct timeval tim;
		//printf("Time to check flow entries!!\n");
		gettimeofday(&tim, NULL);
		double now = tim.tv_sec+(tim.tv_usec/1000000.0);
		DB::iterator dbit = allFlow.begin();
		for(;dbit!= allFlow.end(); dbit++){
			FlowEntry* fe = dbit->second.get();	
			if( fe->status == CHECKING ){
				//didn't receive feeback from switch, assume no flow entry anymore
				ofp_flow_mod* ofm = (ofp_flow_mod*)fe->flow_mod_cmd.get();
				delFlowEntry(&ofm->match, fe->dp);
			}else if( (now - fe->timestamp) > 2*FLOW_TIMEOUT){
				fe->status = CHECKING;
				ofp_flow_mod* ofm = (ofp_flow_mod*)fe->flow_mod_cmd.get();
				//send out messages to check
				size_t size = sizeof(ofp_stats_request) + sizeof(ofp_flow_stats_request);
				boost::shared_array<char> raw_of(new char[size]);
				ofp_stats_request *ost = (ofp_stats_request*)raw_of.get(); 
				ost->header.version = OFP_VERSION;
				ost->header.type = OFPT_STATS_REQUEST;
				ost->header.length = htons(size);
				ost->header.xid = 0;
				ost->type = htons(OFPST_FLOW);
				ost->flags = htons(0);

				ofp_flow_stats_request* ofsr = (ofp_flow_stats_request*)(((uint8_t*)ost->body)+0);
				ofsr->table_id = 0xff; //request for all tables
				memcpy(&ofsr->match, &ofm->match, sizeof(ofp_match));
				ofsr->out_port = OFPP_NONE;

				send_openflow_command(fe->dp, &ost->header, false);
			}
		}
		post(boost::bind(&LinkDB::checkFlowEntry, this), make_timeval(CHECK_INTERVAL,0));
	}
	/**
	 * LinkDB constructor: 
	 *  cleanup thread is created here
	 */
	LinkDB::LinkDB(const Context* c, const xercesc::DOMNode* d)
		       :Component(c), topology(0)
	{
		 //if(!pthread_mutex_init(&db_lock, NULL))
		 //	printf("Could not initialize database lock\n"); 	
		 post(boost::bind(&LinkDB::checkFlowEntry, this), make_timeval(CHECK_INTERVAL, 0));

	}

	LinkDB::~LinkDB(){
	}

	void LinkDB::configure(const container::Configuration*){
		resolve(topology);
		register_handler<Flow_stats_in_event>
			(boost::bind(&LinkDB::handle_flow_stats_in, this, _1));
		register_handler<Flow_mod_event>
		        (boost::bind(&LinkDB::handle_flow_mod, this, _1));
		register_handler<Flow_expired_event>
			(boost::bind(&LinkDB::handle_flow_expired, this, _1));
	}

	/** Return the hash value of one FlowEntry
	 *  @ param: shared_ptr to the FlowEntry
	 *  @ return: hashed value of datapathid and ofp_match inside the FlowEntry
	 */
	uint32_t LinkDB::hash_flow_entry(FlowEntryPtr fe){
		//uint32_t x;
		//x = vigil::fnv_hash(&fe->dp, sizeof(datapathid));
		//x = vigil::fnv_hash(&ofm->match, sizeof(ofp_match), x);
		ofp_flow_mod* ofm = (ofp_flow_mod*)fe->flow_mod_cmd.get();
		return hash_flow_entry(fe->dp, &ofm->match);
	}
	/** Return the hash value of one FlowEntry
	 *  @ param: datapathid, ofp_match in the ofp_flow_mod message
	 *  @ return: hashed value of datapathid and ofp_match inside the FlowEntry
	 */
	uint32_t LinkDB::hash_flow_entry(datapathid dp, ofp_match *ofm){
		uint32_t x;
		//x = vigil::fnv_hash(&dp, sizeof(datapathid));
		
		//x = vigil::fnv_hash(ofm, sizeof(ofp_match), x);
		//x = vigil::fnv_hash(&(ofm->wildcards), sizeof(uint32_t), x);
		//x = vigil::fnv_hash(&(ofm->in_port), sizeof(uint16_t), x);
		ethernetaddr srcmac(ofm->dl_src);
		x = vigil::fnv_hash(&srcmac, sizeof(ethernetaddr));
		ethernetaddr dstmac(ofm->dl_dst);
		x = vigil::fnv_hash(&dstmac, sizeof(ethernetaddr), x);
		x = vigil::fnv_hash(&ofm->dl_vlan, sizeof(uint16_t), x);
		x = vigil::fnv_hash(&ofm->dl_type, sizeof(uint16_t), x);
		x = vigil::fnv_hash(&ofm->nw_proto, sizeof(uint8_t), x);
		x = vigil::fnv_hash(&ofm->nw_src, sizeof(uint32_t), x);
		x = vigil::fnv_hash(&ofm->nw_dst, sizeof(uint32_t), x);
		x = vigil::fnv_hash(&ofm->tp_src, sizeof(uint16_t), x);
		x = vigil::fnv_hash(&ofm->tp_dst, sizeof(uint16_t), x);
		return x;
	}
	/** Return the hash value of one FlowEntry
	 *  @ param: datapathid, ofp_match in the ofp_flow_mod message
	 *  @ return: hashed value of datapathid and ofp_match inside the FlowEntry
	 */
	uint32_t LinkDB::hash_flow_entry(datapathid dp, uint16_t port){
		uint32_t x;
		x = vigil::fnv_hash(&dp, sizeof(datapathid));
		x = vigil::fnv_hash(&port, sizeof(uint16_t), x);
		return x;
	}

	bool LinkDB::get_flowEntry(datapathid dp, uint16_t outport, ofp_match *match, FlowEntryPtr &fep){
		uint32_t hash_dp_port = hash_flow_entry(dp, outport);
		uint32_t hash_value = hash_flow_entry(dp, match);
		LinkTable::iterator ltit = link_table.find(hash_dp_port);
		if( ltit != link_table.end()){
			DB::iterator dbit = ltit->second.find(hash_value);
			if( dbit != ltit->second.end()){
				fep = ltit->second[hash_value];
				return true;
			}else{
				return false;
			}
		}
		return false;
	}
	/** Dump out all the entries is all of the tables
	 */
	void LinkDB::dump_db(void){
		
		log.dbg("link Table:");
		//delete from link database
		LinkTable::iterator ltit = link_table.begin();
		for(; ltit != link_table.end(); ltit++){
			log.dbg("dpid_port:%"PRIu32"", ltit->first);
			DB::iterator dbit = ltit->second.begin();
			for( ; dbit != ltit->second.end(); dbit++){
				FlowEntry* fe = dbit->second.get();
				print_flowEntry(fe);
			}
		}
	}
	/** Print out the value inside a flow entry 
	 *  @ param: shared_ptr to the FlowEntry
	 */
	void LinkDB::print_flowEntry(FlowEntry* fe){
		ofp_flow_mod* ofm = (ofp_flow_mod*)fe->flow_mod_cmd.get();
		datapathid dp = fe->dp;
		uint32_t nw_dst = ofm->match.nw_dst;
		uint32_t nw_src = ofm->match.nw_src;
		ethernetaddr srcmac(ofm->match.dl_src);
		ethernetaddr dstmac(ofm->match.dl_dst);
		uint32_t hash_value = hash_flow_entry(dp, &ofm->match);
		//only for debug purpose
		char ip_dst[30] = "\0";
		char ip_src[30] = "\0";
		snprintf(ip_dst, sizeof ip_dst, "%u.%u.%u.%u",
				((unsigned char *)&nw_dst)[0],
				((unsigned char *)&nw_dst)[1],
				((unsigned char *)&nw_dst)[2],
				((unsigned char *)&nw_dst)[3]);
		snprintf(ip_src, sizeof ip_src, "%u.%u.%u.%u",
				((unsigned char *)&nw_src)[0],
				((unsigned char *)&nw_src)[1],
				((unsigned char *)&nw_src)[2],
				((unsigned char *)&nw_src)[3]);
		log.dbg("entry@%012"PRIx64", port:%"PRIu16" %s("EA_FMT")->%s("EA_FMT") stp %u dtp %u hash:%"PRIu32"\n", 
			dp.as_host(), ntohs(fe->inport), ip_src, EA_ARGS(&(srcmac)),  ip_dst, EA_ARGS(&(dstmac)),
				htons(fe->tp_src), htons(fe->tp_dst), hash_value);

	}

	Disposition LinkDB::handle_flow_stats_in(const Event& e){
		Flow_stats_in_event& fsie = const_cast<Flow_stats_in_event&>(dynamic_cast<const Flow_stats_in_event&>(e));
		datapathid dp = fsie.datapath_id;
		log.dbg("LinkDB: FLOW_STATS @ %012"PRIx64"\t", 
						dp.as_host());
		std::vector<Flow_stats>::iterator fit = fsie.flows.begin();
		while(fit != fsie.flows.end()){
			uint32_t hash_value = hash_flow_entry(dp, &fit->match);
			log.dbg("hash value=%"PRIu32" OK\n", hash_value);
			DB::iterator dbit = allFlow.find(hash_value);
			if(dbit != allFlow.end()){
				FlowEntryPtr fe = allFlow[hash_value];
				fe->status = OK;
			}
			fit++;
		}
		return CONTINUE;
	}

	/** Handle flow expired
	 *  To remove flow from database
	 *  This event need to be enabled in openflow.cc
	 */
	Disposition LinkDB::handle_flow_expired(const Event& e){
		const Flow_expired_event& fee =  dynamic_cast<const Flow_expired_event&>(e);
		datapathid datapath_id = fee.datapath_id;
		ofp_match *ofm = const_cast<ofp_match*>(fee.get_flow());
		uint32_t nw_dst = ofm->nw_dst;
		uint32_t nw_src = ofm->nw_src;
		ethernetaddr srcmac(ofm->dl_src);
		ethernetaddr dstmac(ofm->dl_dst);
		uint32_t hash_value = hash_flow_entry(datapath_id, ofm);
		//only for debug purpose
		char ip_dst[30] = "\0";
		char ip_src[30] = "\0";
		snprintf(ip_dst, sizeof ip_dst, "%u.%u.%u.%u", 
				((unsigned char *)&nw_dst)[0],
				((unsigned char *)&nw_dst)[1],
				((unsigned char *)&nw_dst)[2],
				((unsigned char *)&nw_dst)[3]);
		snprintf(ip_src, sizeof ip_src, "%u.%u.%u.%u", 
				((unsigned char *)&nw_src)[0],
				((unsigned char *)&nw_src)[1],
				((unsigned char *)&nw_src)[2],
				((unsigned char *)&nw_src)[3]);

		//log.dbg("LinkDB: OFPT_FLOW_EXPIRED @ %012"PRIx64", dstip:%s", 
		//				datapath_id.as_host(), ip_dst);

		//log.dbg("Hash Value: %"PRIu32" \n", hash_value);
		//end of debug msg
		
		//delete from link database
		LinkTable::iterator ltit = link_table.begin();
		for(; ltit != link_table.end(); ltit++){
			DB::iterator dbit = ltit->second.find(hash_value);
			if(dbit != ltit->second.end()){
				ltit->second.erase(dbit);
			}
			if(ltit->second.empty()){
				link_table.erase(ltit);
			}

		}
		//pthread_mutex_lock(&db_lock);
		//boost::mutex::scoped_lock lock(db_lock);

		//delete from allFlow
		DB::iterator fit = allFlow.find(hash_value);
		if(fit != allFlow.end()){
			allFlow.erase(fit);
		}
		//pthread_mutex_unlock(&db_lock);
		log.dbg("After Flow_Expired:\n");
		dump_db();
		return CONTINUE;
	}

	/** Handle flow mod event and add associated ofp_mod_cmd to database tables
	 *  Broadcast messages will be discarded, and 
	 *  Only event triggered from Edge Switches will be handled
	 */
	Disposition LinkDB::handle_flow_mod(const Event& e){
		//const Flow_in_event& fi = assert_cast<const Flow_in_event&>(e);
		const Flow_mod_event& fme = dynamic_cast<const Flow_mod_event&>(e);
		const ofp_flow_mod *ofm = fme.get_flow_mod();
		datapathid datapath_id = fme.datapath_id;
		uint16_t inport = ntohs(ofm->match.in_port);
		size_t size = ntohs(ofm->header.length);
		
		//printf("FLOWDB: handle_flow_mod\n");
		//number of actions in the ofp_flow_mod
		double n_action  = ((ntohs(ofm->header.length) - sizeof(ofp_flow_mod))/sizeof(ofp_action_output));
		uint32_t nw_dst = ofm->match.nw_dst;
		uint32_t nw_src = ofm->match.nw_src;
		ethernetaddr srcmac(ofm->match.dl_src);
		ethernetaddr dstmac(ofm->match.dl_dst);

		if( ofm->header.type == OFPT_FLOW_MOD && n_action > 0 && nw_dst > 0 && nw_src > 0){
			if( ofm->command == OFPFC_ADD || ofm->command == OFPFC_MODIFY 
						      || ofm->command == OFPFC_MODIFY_STRICT){
				if( !srcmac.is_multicast() && !dstmac.is_multicast()
						&& nw_src != 0 && nw_dst != 0 
						&& ofm->match.dl_type == htons(0x0800)	){


					bool is_internal = false;
					//check inport and each outport to see whether it is internal 
					is_internal = topology->is_internal(datapath_id, inport);
					vector<uint16_t> outport_list;
					int i = 1;
					while( i <= n_action ){
						ofp_action_output *action = (ofp_action_output*) 
							(((uint8_t*)ofm->actions) + (i-1)* sizeof(ofp_action_output));
						if(action->type == htons(OFPAT_OUTPUT)){
							outport_list.push_back(ntohs(action->port));
							if( is_internal == true ){
								is_internal = topology->is_internal(datapath_id, ntohs(action->port));
							}
						}
						i++;
					}


					//add these into the flow database
					if( (EDGEONLY && !is_internal) || !EDGEONLY ){
						//copy and save the ofm as a whole
						boost::shared_array<char> raw_of(new char[size]);
						ofp_flow_mod *new_ofm = (ofp_flow_mod*) raw_of.get();
						memcpy(new_ofm, ofm, size);

						FlowEntryPtr fe(new FlowEntry);
						fe->flow_mod_cmd = raw_of;
						fe->dp = datapath_id;
						fe->status = OK;
						fe->srcip = nw_src ; //network order
						fe->dstip = nw_dst ; //network order
						fe->srcmac = srcmac;
						fe->dstmac = dstmac;
						fe->inport = ofm->match.in_port;
						fe->tp_src = ofm->match.tp_src;
						fe->tp_dst = ofm->match.tp_dst;
						struct timeval tim;
						gettimeofday(&tim, NULL);
						fe->timestamp = tim.tv_sec+(tim.tv_usec/1000000.0);
						
						uint32_t hash_value = hash_flow_entry(fe);
						fe->flow_hash = hash_value;

						//add into link table
						vector<uint16_t>::iterator vit = outport_list.begin();
						for(;vit != outport_list.end(); vit++){
							uint32_t hash_dp_port = hash_flow_entry(datapath_id, *vit);
							LinkTable::iterator ltit = link_table.find(hash_dp_port);
							if( ltit == link_table.end()){
								//create new map
								DB db;
								db[hash_value] = fe;
								link_table[hash_dp_port] = db;
							}else{
								ltit->second[hash_value] = fe;
							}
						}

						//add to allFlow table
						allFlow[hash_value] = fe;
					}
				}
			}else if(ofm->command == OFPFC_DELETE || ofm->command == OFPFC_DELETE_STRICT){
				//delete from table
				ofp_match* match = const_cast<ofp_match*>(&ofm->match);
				delFlowEntry(match, datapath_id);
			}
		}//end of OFPT_FLOW_MOD
		log.dbg("After Add");
		dump_db();
		return CONTINUE;
	}

	void LinkDB::delFlowEntry(ofp_match *match, datapathid dp){
		ethernetaddr srcmac(match->dl_src);
		ethernetaddr dstmac(match->dl_dst);
		uint32_t hash_value = hash_flow_entry(dp, match);
		
		
		//delete from link database
		LinkTable::iterator ltit = link_table.begin();
		for(; ltit != link_table.end(); ltit++){
			DB::iterator dbit = ltit->second.find(hash_value);
			if(dbit != ltit->second.end()){
				ltit->second.erase(dbit);
			}
			if(ltit->second.empty()){
				link_table.erase(ltit);
			}
		}
		//delete from allFlow
		DB::iterator fit = allFlow.find(hash_value);
		if(fit != allFlow.end()){
			allFlow.erase(fit);
		}
		log.dbg("After Delete");
		dump_db();

	}
     }//end of application
}//end of vigil

REGISTER_COMPONENT(vigil::container::Simple_component_factory
		<vigil::applications::LinkDB>,
		vigil::applications::LinkDB);

